﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.Linq;
using Newtonsoft.Json;
using BCCommon;
using BCCommon.Enums.Chats;
using BCCommon.Exceptions;
using BCCommon.Extensions;
using BCEntity.Chats;
using BCData.Chats;
using BCDto.Chats.Message;
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json.Converters;
using System.Globalization;

namespace BCService.Chats
{
    public class MessageService : IMessageService
    {
        private readonly IWebSocketContextService webSocketContextService;
        private readonly IApplicationContextService applicationContextService;
        private readonly IMessageData messageData;
        private readonly IMessageReceiverData messageReceiverData;
        private readonly IThreadData threadData;
        private readonly IThreadParticipantData threadParticipantData;
        private readonly IUserResolverService userResolverService;

        private readonly JsonSerializerSettings JsonSerializerSettings = new JsonSerializerSettings
        {
            ContractResolver = new DefaultContractResolver { NamingStrategy = new SnakeCaseNamingStrategy { ProcessDictionaryKeys = false } },
            Converters = new List<JsonConverter> {
                new StringEnumConverter { AllowIntegerValues = true },
                new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeLocal, DateTimeFormat = "yyyy-MM-dd HH:mm:ss" }
            },
            NullValueHandling = NullValueHandling.Ignore
        };

        public MessageService(IWebSocketContextService webSocketContextService,
            IApplicationContextService applicationContextService,
            IMessageData messageData,
            IMessageReceiverData messageReceiverData,
            IThreadData threadData,
            IThreadParticipantData threadParticipantData,
            IUserResolverService userResolverService)
        {
            this.webSocketContextService = webSocketContextService;
            this.applicationContextService = applicationContextService;
            this.messageData = messageData;
            this.messageReceiverData = messageReceiverData;
            this.threadData = threadData;
            this.threadParticipantData = threadParticipantData;
            this.userResolverService = userResolverService;
        }

        public IEnumerable<MessageDto> GetMessages(long threadId, long? minMessageId, long? maxMessageId, int count)
        {
            var list = messageData.GetMessagesAsync(applicationContextService.GetChatUserId(), threadId, minMessageId, maxMessageId, count).Result
                .Reverse();
            var messageReceiverCollection = messageReceiverData.GetMessageReceiversByMessageIdAsync(list.Select(p => p.MessageId)).Result
                .GroupBy(p => p.MessageId)
                .ToDictionary(p => p.Key, p => p.AsEnumerable());
            var participants = threadParticipantData.GetThreadParticipantsByThreadIdAsync(new[] { threadId }).Result;

            return list.Select(message =>
            {
                if (!messageReceiverCollection.TryGetValue(message.MessageId, out var messageReceivers))
                {
                    messageReceivers = new MessageReceiverEntity[0];
                }

                return this.ConvertToMessageDto(message, messageReceivers, participants);
            });
        }

        public MessageDto GetMessage(long messageId)
        {
            var message = messageData.GetMessageAsync(messageId).Result;
            var messageReceivers = messageReceiverData.GetMessageReceiversByMessageIdAsync(new[] { messageId }).Result;

            if (messageReceivers.FirstOrDefault(p => p.ReceiveUserId == applicationContextService.GetChatUserId()) == null)
            {
                throw new ObjectNotFoundException();
            }

            return this.ConvertToMessageDto(message, messageReceivers);
        }

        public MessageDto Create(MessageRequestDto requestDto)
        {
            var thread = threadData.GetThreadAsync(requestDto.ThreadId).Result;

            if (thread == null)
            {
                throw new ArgumentException("找不到指定的会话，或者你不是指定会话的参与者。");
            }

            if (thread.Status != ThreadStatus.Chating.ToString())
            {
                throw new ArgumentException($"消息发送失败，因为当前会话 {thread.Status.AsEnum<ThreadStatus>().GetDisplayString()}。");
            }

            var participants = threadParticipantData.GetThreadParticipantsByThreadIdAsync(new[] { requestDto.ThreadId }).Result;
            var self = participants.FirstOrDefault(p => p.ParticipantUserId == applicationContextService.GetChatUserId());

            if (self == null)
            {
                throw new ArgumentException("找不到指定的会话，或者你不是指定会话的参与者。");
            }

            var message = this.ConvertToMessageDto(this.CreateMessage(thread, participants, requestDto), participants: participants);

            if (thread.Type == ThreadType.CustomerService.ToString())
            {
                thread.GetService(applicationContextService.RequestServices)?.ReceiveNewMessage(message);
            }

            this.SendToWebSocketAsync(message, participants);

            return message;
        }

        public void Delete(long messageId)
        {
            var messageReceiver = messageReceiverData.GetMessageReceiverAsync(applicationContextService.GetChatUserId(), messageId).Result;

            if (messageReceiver == null)
            {
                throw new ArgumentException("你无权删除非本人的消息。");
            }

            messageReceiverData.SetAsDeleted(applicationContextService.GetChatUserId(), messageId);
        }

        public MessageDto Revoke(long messageId)
        {
            var message = messageData.GetMessageAsync(messageId).Result;
            var messageReceivers = messageReceiverData.GetMessageReceiversByMessageIdAsync(new[] { messageId }).Result;

            if (messageReceivers.FirstOrDefault(p => p.ReceiveUserId == applicationContextService.GetChatUserId()) == null)
            {
                throw new ObjectNotFoundException();
            }

            if (message.CreateUserId != applicationContextService.GetChatUserId())
            {
                throw new ArgumentException("你无权撤回非本人的消息。");
            }

            if (message.CreateTime.AddMinutes(2.5) < DateTime.Now)
            {
                throw new ArgumentException("消息发出已超过两分钟，不能撤回。");
            }

            message.Deleted = true;

            return this.ConvertToMessageDto(messageData.UpdateAsync(message).Result, messageReceivers);
        }

        public MessageDto Receipt(long messageId)
        {
            messageReceiverData.UpdateReceiptTimeAsync(applicationContextService.GetChatUserId(), messageId);

            return this.GetMessage(messageId);
        }

        public MessageDto Process(long messageId)
        {
            var message = messageData.GetMessageAsync(messageId).Result;
            var messageReceivers = messageReceiverData.GetMessageReceiversByMessageIdAsync(new[] { messageId }).Result;

            if (messageReceivers.FirstOrDefault(p => p.ReceiveUserId == applicationContextService.GetChatUserId()) == null)
            {
                throw new ObjectNotFoundException();
            }

            message.Processed = true;

            return this.ConvertToMessageDto(messageData.UpdateAsync(message).Result, messageReceivers);
        }

        //public MessageDto MarkAsRead(long messageId, MarkAsReadRequestDto requestDto)
        //{
        //    if (requestDto.MarkPerviousAsRead)
        //    {
        //        messageReceiverData.UpdateAllReadTimeAsync(messageId, currentChatUserId);
        //    }
        //    else
        //    {
        //        messageReceiverData.UpdateReadTimeAsync(messageId, currentChatUserId);
        //    }

        //    return this.GetMessage(messageId);
        //}

        public int GetUnreadCount()
        {
            var unreadCounts = messageData.GetUnreadMessageCountAsync(applicationContextService.GetChatUserId(), null).Result;

            if (!unreadCounts.Any())
            {
                return 0;
            }

            return unreadCounts.Sum(p => p.Value);
        }

        private MessageEntity CreateMessage(ThreadEntity thread, IEnumerable<ThreadParticipantEntity> participants, MessageRequestDto requestDto)
        {
            var message = messageData.InsertAsync(new MessageEntity
            {
                ThreadId = requestDto.ThreadId,
                Type = requestDto.MessageType.ToString(),
                Content = requestDto.GetPreviewText(),
                RawData = JsonConvert.SerializeObject(requestDto, JsonSerializerSettings),
                CreateTime = DateTime.Now,
                CreateUserId = applicationContextService.GetChatUserId()
            }).Result;
            messageReceiverData.InsertAllAsync(participants.Select(p => new MessageReceiverEntity
            {
                ThreadId = requestDto.ThreadId,
                MessageId = message.MessageId,
                ReceiveType = MessageReceiveType.Push.ToString(),
                ReceiveUserId = p.ParticipantUserId,
                CreateTime = DateTime.Now
            }));

            // 更新会话上的最后聊天记录。
            if (requestDto.MessageType != MessageType.Action && requestDto.MessageType != MessageType.RawData)
            {
                thread.LastChatContent = message.Content;
                thread.LastChatTime = message.CreateTime;
                thread.LastChatUserId = message.CreateUserId;

                threadData.UpdateAsync(thread);
            }

            return message;
        }

        private MessageDto ConvertToMessageDto(MessageEntity entity, IEnumerable<MessageReceiverEntity> messageReceivers = null, IEnumerable<ThreadParticipantEntity> participants = null)
        {
            if (messageReceivers == null)
            {
                messageReceivers = messageReceiverData.GetMessageReceiversByMessageIdAsync(new[] { entity.MessageId }).Result;
            }

            if (participants == null)
            {
                participants = threadParticipantData.GetThreadParticipantsByThreadIdAsync(new[] { entity.ThreadId }).Result;
            }

            var messageDto = JsonConvert.DeserializeObject<MessageDto>(entity.RawData, JsonSerializerSettings);

            messageDto.MessageId = entity.MessageId;
            messageDto.Content = entity.Content;
            messageDto.Recvicers = messageReceivers.Select(p => new MessageReceiverDto
            {
                ReceiveType = p.ReceiveType.AsEnum<MessageReceiveType>(),
                SendTime = p.SendTime,
                ReceiptTime = p.ReceiptTime,
                ReadTime = p.ReadTime,
                User = userResolverService.Resolve(p.ReceiveUserId).AsParticipantDto(participants.FirstOrDefault(c => c.ParticipantUserId == p.ReceiveUserId))
            });
            messageDto.Processed = entity.Processed;
            messageDto.Deleted = entity.Deleted;
            messageDto.CreateTime = entity.CreateTime;
            messageDto.CreateUser = userResolverService.Resolve(entity.CreateUserId).AsParticipantDto(participants.FirstOrDefault(p => p.ParticipantUserId == entity.CreateUserId));

            return messageDto;
        }

        private void SendToWebSocketAsync(MessageDto messageDto, IEnumerable<ThreadParticipantEntity> participants)
        {
            Task.Run(delegate
            {
                string jsonString = JsonConvert.SerializeObject(messageDto, JsonSerializerSettings);

                foreach (var item in participants)
                {
                    this.webSocketContextService.SendTextAsync(jsonString, item.ParticipantUserId);
                }
            });
        }
    }
}
